﻿using System;
using System.IO;

namespace Gargoyle.Roton
{
    public static class AudioWaveGen
    {
        public enum WaveTypes
        {
            Unknown,
            PureSawtooth,
            PureSine,
            PureSquare,
            PureTriangle,
            RampedSquare,
            Squaretooth
        }

        public static short[] GenerateWave(WaveTypes WaveType, int SampleRate, double Frequency, int Length, short Amplitude) {
            short[] Samples = new short[Length];
            switch(WaveType)
            {
                case WaveTypes.PureSawtooth:
                    PureSawtooth(ref Samples, SampleRate, Frequency, Length, Amplitude);
                    break;
                case WaveTypes.PureSine:
                    PureSine(ref Samples, SampleRate, Frequency, Length, Amplitude);
                    break;
                case WaveTypes.PureSquare:
                    PureSquare(ref Samples, SampleRate, Frequency, Length, Amplitude);
                    break;
                case WaveTypes.PureTriangle:
                    PureTriangle(ref Samples, SampleRate, Frequency, Length, Amplitude);
                    break;
                case WaveTypes.RampedSquare:
                    RampedSquare(ref Samples, SampleRate, Frequency, Length, Amplitude);
                    break;
                case WaveTypes.Squaretooth:
                    Squaretooth(ref Samples, SampleRate, Frequency, Length, Amplitude);
                    break;
            }

            return Samples;
        }

        private static void PureSawtooth(ref short[] Samples, int SampleRate, double Frequency, int Length, short Amplitude)
        {
            double t = (Math.PI * 2 * Frequency) / SampleRate;
            int phaseLength = Convert.ToInt32(SampleRate / Frequency);
            short step = Convert.ToInt16((Amplitude * 2) / phaseLength);
            short sample;
            int samplesOut = 0;

            while(samplesOut < Length)
            {
                sample = Convert.ToInt16(-Amplitude);

                for(int i=0; i < phaseLength && samplesOut < Length; i++)
                {
                    sample += step;
                    Samples[samplesOut] = sample;
                    samplesOut++;
                }
            }
        }

        private static void PureSine(ref short[] Samples, int SampleRate, double Frequency, int Length, short Amplitude)
        {
            double t = (Math.PI * 2 * Frequency) / SampleRate;
            for(int i = 0; i < Length; i++)
                Samples[i] = (short)(Math.Sin(i * t) * Amplitude);
        }

        private static void PureSquare(ref short[] Samples, int SampleRate, double Frequency, int Length, short Amplitude)
        {
            double t = (Math.PI * 2 * Frequency) / SampleRate;
            for(int i = 0; i < Length; i++)
                Samples[i] = (short)((Math.Sign(Math.Sin(i * t))) * Amplitude);
        }

        private static void PureTriangle(ref short[] Samples, int SampleRate, double Frequency, int Length, short Amplitude)
        {
            double t = (Math.PI * 2 * Frequency) / SampleRate;
            int phaseLength = Convert.ToInt32(SampleRate / Frequency);
            short step = Convert.ToInt16((Amplitude * 2) / phaseLength);
            short sample = 0;

            for(int i = 0; i < Length; i++)
            {
                if(Math.Abs((int)sample + step) > Amplitude)
                    step = (short)-step;

                sample += step;
                Samples[i] = sample;
            }
        }

        private static void RampedSquare(ref short[] Samples, int SampleRate, double Frequency, int Length, short Amplitude)
        {
            double t = (Math.PI * 2 * Frequency) / SampleRate;
            int Polarity = 1;
            int PolarityCount = 0;
            double Adjustment = 0.0025;
            double outSample;

            for(int i = 0; i < Length; i++)
            {
                outSample = Math.Sign(Math.Sin(i * t));
                if(outSample > 0 && Polarity == 1)
                {
                    Polarity = -1;
                    PolarityCount = 0;
                }
                else if(outSample < 0 && Polarity == -1)
                {
                    Polarity = 1;
                    PolarityCount = 0;
                }

                if(outSample != 0)
                    outSample += Polarity * (Adjustment * PolarityCount++);
                Samples[i] = (short)(outSample * Amplitude);
            }
        }

        private static void Squaretooth(ref short[] Samples, int SampleRate, double Frequency, int Length, short Amplitude)
        {
            const double ampSquare = 0.8;
            const double ampSawtooth = 0.2;

            short[] waveSquare = new short[Length];
            short[] waveSawtooth = new short[Length];

            PureSquare(ref waveSquare, SampleRate, Frequency, Length, Amplitude);
            PureSawtooth(ref waveSawtooth, SampleRate, Frequency, Length, Amplitude);

            for(int i = 0; i < Length; i++)
                Samples[i] = (short)((waveSquare[i] * ampSquare) + (waveSawtooth[i] * ampSawtooth));
        }
    }
}
